/*
   Copyright (c) [2023] [魔凤啸天]
   [MFUI] is licensed under Mulan PSL v2.
   You can use this software according to the terms and conditions of the Mulan PSL v2. 
   You may obtain a copy of Mulan PSL v2 at:
            http://license.coscl.org.cn/MulanPSL2 
   THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.  
   See the Mulan PSL v2 for more details.  
*/
#include "textedit.hpp"

namespace MFUI{
	SDL_Cursor* TextEdit::cursorcache=nullptr;
	unsigned int TextEdit::nvisit=0;
	void TextEdit::texteditinputcallback(Window* win,TextEdit* v,SDL_Event* e){
		v->replace(v->begincursor,v->endcursor,e->text.text);
	}
	void TextEdit::texteditinputeditcallback(Window* win,TextEdit* v,SDL_Event* e){
		v->edittext=e->edit.text;
		v->_updatebase();
	}
	void TextEdit::backspace(){
		if(begincursor!=endcursor){
			replace(begincursor,endcursor,emptytext);
			return;
		}
		if(cursor.size()>1)replace(begincursor-1,endcursor,emptytext);
	}
	void TextEdit::texteditkeyboardcallback(Window* win,TextEdit* v,SDL_Event* e){
		auto& b=e->key;
		auto mod=SDL_GetModState();
		if(mod==KMOD_LCTRL||mod==KMOD_RCTRL){
			switch(b.keysym.scancode){
				case SDL_SCANCODE_A:v->selectall();return;
				case SDL_SCANCODE_C:{
					std::string s;
					v->getselected(s);
					if(s.size())SDL_SetClipboardText(s.c_str());
					return;
				}
				case SDL_SCANCODE_X:{
					std::string s;
					v->getselected(s);
					if(s.size()){
						SDL_SetClipboardText(s.c_str());
						v->replace(v->begincursor,v->endcursor,emptytext);
						v->mousecursor=v->endcursor;
					}
					return;
				}
				case SDL_SCANCODE_V:{
					auto s=SDL_GetClipboardText();
					if(*s){
						v->replace(v->begincursor,v->endcursor,s);
						v->mousecursor=v->endcursor;
						SDL_free(s);
					}
					return;
				}
				default:;
			}
			return;
		}
		switch(b.keysym.scancode){
			case SDL_SCANCODE_BACKSPACE:v->backspace();return;
			case SDL_SCANCODE_RETURN:
			case SDL_SCANCODE_RETURN2:
			if(v->enter)win->unsetfocus(v);
			return;
			case SDL_SCANCODE_LEFT:
			if(mod==KMOD_LSHIFT||mod==KMOD_RSHIFT){
				if(v->refcursor<v->endcursor){
					--v->endcursor;
					v->righthand=true;
					v->_updatebase();
					return;
				}
				v->righthand=false;
				if(v->begincursor)--v->begincursor,v->mousecursor=v->endcursor,v->_updatebase();
				return;
			}
			if(v->endcursor)v->begincursor=--v->endcursor,v->mousecursor=v->endcursor,v->_updatebase();
			return;
			case SDL_SCANCODE_RIGHT:
			if(mod==KMOD_LSHIFT||mod==KMOD_RSHIFT){
				if(v->begincursor<v->refcursor){
					++v->begincursor;
					v->righthand=false;
					v->_updatebase();
					return;
				}
				v->righthand=true;
				if(v->endcursor<v->cursor.size()-1)++v->endcursor,v->mousecursor=v->endcursor,v->_updatebase();
				return;
			}
			if(v->endcursor<v->cursor.size()-1){
				v->begincursor=++v->endcursor,v->_updatebase();
				return;
			}
			v->begincursor=v->endcursor,v->mousecursor=v->endcursor,v->_updatebase();
			return;
			default:;
		}
	}
	TextEdit::~TextEdit(){}
	unsigned int TextEdit::startinput=0;
	static size_t utf8len(const std::string& t){
		size_t n=0;
		auto s=t.c_str();
		for(;;){
			auto c=*s;
			if(c){
				auto l=utf8len(c);
				if(l){
					n++;
					s+=l;
					continue;
				}
			}
			return n;
		}
	}
	void TextEdit::_updatetext(){
		cursor.clear();
		font->htextunits(text,fontsize,cursor);
		_validatecursor();
		_updatebase();
		ontextchanged();
	}
	void TextEdit::draw(Renderer* r,bool updatecache){
		_draw(r,updatecache);
		r->setdrawcolor(background);
		r->fillrectF(&rect);
		bool foc=focused();
		if(foc){
			r->setdrawcolor({hightlight.r,hightlight.g,hightlight.b,(Uint8)(hightlight.a>>1)});
			r->fillrectF(&rect);
		}
		margin=rect.w*0.025F;
		if(text.size()){
			auto l=righthand?endcursor:begincursor;
			auto w=rect.w-margin-margin;
			auto cur=&cursor[l];
			auto i=l;
			int sum=0;
			textrect.w=w,textrect.h=(float)font->height(fontsize);
			while(i){
				auto last=&cursor[--i];
				auto v=sum+(cur->beginpos-last->beginpos);
				if(v>=w)break;
				sum=v;
				cur=last;
			}
			leftpoint=i;
			auto distance=l-i;
			i=l;
			cur=&cursor[i];
			l=cursor.size()-1;
			while(i<l){
				auto next=&cursor[++i];
				auto v=sum+(next->beginpos-cur->beginpos);
				if(v>=w){
					break;
				}
				sum=v;
				cur=next;
			}
			rightpoint=i;
			size_t editdelta=cursor[distance].begin;
			auto s=text.c_str();
			auto leftdraw=s+cursor[leftpoint].begin,rightdraw=s+cursor[i].begin;
			std::vector<Font::TextUnit> editunits;
			auto editsize=edittext.size();
			std::string text((rightdraw-leftdraw)+editsize,0);
			auto tp=text.begin();
			std::copy(leftdraw,leftdraw+editdelta,tp);
			tp+=editdelta;
			std::copy(edittext.begin(),edittext.end(),tp);
			std::copy(leftdraw+editdelta,rightdraw,tp+editsize);
			font->htextunits(text,fontsize,editunits);
			l=editunits.size()-1;
			editdelta=l-(rightpoint-leftpoint);
			cur=&editunits[distance];
			i=distance;
			sum=0;
			s=text.c_str();
			while(i){
				auto last=&editunits[--i];
				auto v=sum+(cur->beginpos-last->beginpos);
				if(v>=w)break;
				sum=v;
				cur=last;
			}
			leftdraw=s+editunits[i].begin;
			size_t leftpoint=i;
			i=distance;
			cur=&editunits[i];
			while(i<l){
				auto next=&editunits[++i];
				auto v=sum+(next->beginpos-cur->beginpos);
				if(v>=w){
					break;
				}
				sum=v;
				cur=next;
			}
			rightdraw=s+editunits[i].begin;
			size_t rightpoint=i;
			font->hsize(textrect,leftdraw,rightdraw,fontsize,textrect);
			size_t begincursor,endcursor;
			if(this->begincursor<leftpoint)begincursor=leftpoint;
			else if(this->begincursor>rightpoint)begincursor=rightpoint;
			else begincursor=this->begincursor-leftpoint;
			if(this->endcursor>rightpoint)endcursor=rightpoint;
			else endcursor=this->endcursor-leftpoint;
			SDL_FRect _;
			float editbegin=editunits[distance].beginpos,editend=editunits[distance+editdelta].beginpos;
			float editvalue=editend-editbegin;
			_.x=editunits[begincursor].beginpos;
			_.h=textrect.h;
			getcentery(&rect,&textrect,&_.y);
			if(begincursor!=endcursor){
				_.w=editunits[endcursor+editdelta].beginpos-_.x-editvalue;
				if(righthand)_.x=rect.x+margin+_.x;
				else _.x=rect.x+margin+_.x+editvalue;
				r->setdrawcolor(hightlight);
				r->fillrectF(&_);
			}
			else _.w=0.f,_.x=rect.x+margin+_.x;
			if(foc){
				if(dragged){
					if(mousecursor>refcursor)_.x=rect.x+margin+(editunits[mousecursor].beginpos-textrect.x);
					else _.x=rect.x+margin+(editunits[mousecursor].beginpos-textrect.x)-2;
				}
				else if(righthand)_.x+=_.w+editvalue;
				r->setdrawcolor(textcolor);
				r->drawlineF(_.x,_.y,_.x,_.y+_.h);
				_.w=rect.w;
				SDL_Rect __{(int)_.x,(int)_.y,(int)_.w,(int)_.h};
				SDL_SetTextInputRect(&__);
				auto x=editbegin+rect.x+margin,y=_.y+_.h;
				r->drawlineF(x,y,x+(editend-editbegin),y);
			}
			_.x=rect.x+margin,_.w=rect.w-margin-margin;
			font->hdraw(r,_,text,fontsize,textcolor);
			makecircle(r,rect);
			return;
		}
		if(edittext.empty()){
			auto alpha=textcolor.a;
			textcolor.a>>=1;
			SDL_FRect _;
			auto w=rect.w-margin-margin;
			_.x=rect.x+margin,_.w=w,_.h=rect.h;
			font->hsize(_,hinttext,fontsize,_);
			getcentery(&rect,&_,&_.y);
			_.w=w;
			font->hdraw(r,_,hinttext,fontsize,textcolor);
			textcolor.a=alpha;
			SDL_Rect __{(int)_.x,(int)_.y,(int)_.w,(int)_.h};
			SDL_SetTextInputRect(&__);
			makecircle(r,rect);
			return;
		}
		SDL_FRect _;
		auto w=rect.w-margin-margin;
		_.x=rect.x+margin,_.w=w,_.h=rect.h;
		font->hsize(_,edittext,fontsize,_);
		auto tw=_.w;
		getcentery(&rect,&_,&_.y);
		_.w=w;
		font->hdraw(r,_,edittext,fontsize,textcolor);
		SDL_Rect __{(int)_.x,(int)_.y,(int)_.w,(int)_.h};
		SDL_SetTextInputRect(&__);
		auto y=_.y+_.h;
		r->setdrawcolor(textcolor);
		r->drawlineF(_.x,y,_.x+tw,y);
		makecircle(r,rect);
	}
	void TextEdit::sethint(const std::string& hint){
		this->hinttext=hint;
		_updatebase();
	}
	const std::string& TextEdit::gethint(){return hinttext;}
	void TextEdit::ontextchanged(){}
	TextEdit::TextEdit(const std::string& text,const std::string& holder,
	Font* font,int fontsize,SDL_Color textcolor,SDL_Color background,
	SDL_Color hightlight):TextButton(text,font,fontsize,textcolor,background),
	hightlight(hightlight),righthand(true),enter(true),
	hinttext(holder),begincursor(0),endcursor(0),leftpoint(0),rightpoint(0),
	dragged(false),lastcursor(-1),refcursor(0){
		font->htextunits(text,fontsize,cursor);
	}
	void TextEdit::_onfocus(Window* w){
		if(!startinput++){
			SDL_StartTextInput();
			SDL_FRect r;
			r.x=rect.x+margin+textrect.w;
			r.w=rect.w,r.h=textrect.h;
			getcentery(&rect,&textrect,&r.y);
			SDL_Rect _{(int)r.x,(int)r.y,(int)r.w,(int)r.h};
			SDL_SetTextInputRect(&_);
		}
		w->connect(SDL_TEXTINPUT,this,(Window::EventCallback)texteditinputcallback);
		w->connect(SDL_TEXTEDITING,this,(Window::EventCallback)texteditinputeditcallback);
		w->connect(SDL_KEYDOWN,this,(Window::EventCallback)texteditkeyboardcallback);
		_updatebase();
	}
	void TextEdit::_losefocus(Window* w){
		if(!--startinput){
			SDL_StopTextInput();
			edittext.clear();
		}
		w->disconnect(SDL_TEXTINPUT,this);
		w->disconnect(SDL_KEYDOWN,this);
		w->disconnect(SDL_TEXTEDITING,this);
		_updatebase();
	}
	void TextEdit::onremove(Window* w){
		TextButton::onremove(w);
		SDL_SetCursor(cursorcache),cursorcache=nullptr;
	}
	void TextEdit::_released(Window* w,SDL_FPoint p){
		if(dragged){
			_calcmousecursor(p.x-rect.x);
			dragged=false;
			if(ismouseentered())SDL_SetCursor(cursor_ibem);
			if(mousecursor<begincursor){
				auto& a=cursor;
				utilmoveleft(text,a[begincursor].begin,
				a[endcursor].begin,a[mousecursor].begin);
				endcursor=mousecursor+(endcursor-begincursor);
				begincursor=mousecursor;
				_updatetext();
				return;
			}
			else if(endcursor<mousecursor){
				auto& a=cursor;
				utilmoveright(text,a[begincursor].begin,
				a[endcursor].begin,a[mousecursor].begin);
				begincursor=mousecursor-(endcursor-begincursor);
				endcursor=mousecursor;
				_updatetext();
				return;
			}
			if(begincursor!=endcursor){
				endcursor=mousecursor,begincursor=mousecursor;
				_updatebase();
			}
			return;
		}
		if(mousecursor==refcursor&&begincursor!=mousecursor){
			endcursor=mousecursor,begincursor=mousecursor;
			_updatebase();
		}
	}
	void TextEdit::_pressed(Window* w,SDL_FPoint p){
		_calcmousecursor(p.x-rect.x);
		refcursor=mousecursor;
		if(begincursor<=mousecursor&&mousecursor<endcursor){
			dragged=true;
			SDL_SetCursor(cursorcache);
		}
	}
	void TextEdit::mousemove(Window* w,SDL_FPoint p){
		if(getstate()==state_pressed){
			_calcmousecursor(p.x-rect.x);
			if(!dragged){
				if(mousecursor<refcursor){
					begincursor=mousecursor,endcursor=refcursor;
					if(mousecursor<refcursor)righthand=false;else righthand=true;
					if(mousecursor!=lastcursor)lastcursor=mousecursor,_updatebase();
					return;
				}
				begincursor=refcursor,endcursor=mousecursor;
			}
			if(mousecursor<refcursor)righthand=false;else righthand=true;
			if(mousecursor!=lastcursor)lastcursor=mousecursor,_updatebase();
		}
	}
	void TextEdit::selectall(){
		begincursor=0,endcursor=cursor.size()-1,righthand=true;
		refcursor=0,_updatebase();
	}
	void TextEdit::setselected(size_t begin,size_t end){
		begincursor=begin,endcursor=end,_validatecursor(),_updatebase();
	}
	void TextEdit::getselected(std::string& out){
		auto _=cursor.begin(),b=_+begincursor,e=_+endcursor;
		auto c=text.begin();
		out.assign(c+b->begin,c+e->begin);
	}
	void TextEdit::getcursor(size_t& begin,size_t& end){begin=begincursor,end=endcursor;}
	size_t TextEdit::insert(size_t pos,const std::string& t){
		auto l=cursor.size();
		if(pos<l){
			pos=cursor[pos].begin;
			utilreplacestring(text,pos,pos,t);
			_updatetext();
			return pos+(cursor.size()-l);
		}
		text.append(t);
		_updatetext();
		return cursor.size()-1;
	}
	void TextEdit::append(const std::string& t){text.append(t);_updatetext();}
	void TextEdit::clear(){
		text.clear();
		_updatetext();
	}
	size_t TextEdit::length()const{return cursor.size()-1;}
	void TextEdit::getcontent(size_t begin,size_t end,std::string& out){
		begincursor=begin,endcursor=end,_validatecursor();
		getselected(out);
	}
	size_t TextEdit::replace(const std::string& target,const std::string& t,size_t n){
		auto ret=utilreplace(text,target,t,n);
		_updatetext();
		return ret;
	}
	size_t TextEdit::replace(size_t begin,size_t end,const std::string& t){
		begincursor=begin,endcursor=end;
		_validatecursor();
		auto& arr=cursor;
		utilreplacestring(text,arr[begincursor].begin,arr[endcursor].begin,t);
		_updatetext();
		begincursor+=utf8len(t);
		endcursor=begincursor;
		refcursor=endcursor;
		return endcursor;
	}
	void TextEdit::mouseentered(Window* w){
		if(!nvisit++)cursorcache=SDL_GetCursor(),SDL_SetCursor(cursor_ibem);
	}
	void TextEdit::mouseexited(Window* w){
		if(!--nvisit)SDL_SetCursor(cursorcache),cursorcache=nullptr;
	}
	void TextEdit::_validatecursor(){
		auto l=cursor.size()-1;
		if(begincursor>l)begincursor=l;
		if(endcursor>l)endcursor=l;
		if(endcursor<begincursor)endcursor=begincursor;
	}
	void TextEdit::_calcmousecursor(float mousepoint){
		auto v=mousepoint-margin;
		if(v>0.F){
			auto& arr=cursor;
			if(v<textrect.w){
				size_t l=leftpoint,r=rightpoint,i=(l+r)>>1;
				//二分查找
				auto cur=&arr[i];//头
				auto p=arr[l].beginpos+v;
				while(l<r){
					auto _p=cur->beginpos;
					auto pos=_p+((arr[i+1].beginpos-_p)*0.5f);
					if(p<pos){
						r=i;
						i=(l+r)>>1;
						cur=&arr[i];
						continue;
					}
					if(pos<p){
						l=i+1;
						i=(l+r)>>1;
						cur=&arr[i];
						continue;
					}
					mousecursor=i;
					return;
				}
				mousecursor=i;
				return;
			}
			//右侧后向
			mousecursor=rightpoint;
			return;
		}
		//左侧前向
		mousecursor=leftpoint;
	}
}